/*
 * Config contains all configuration elements for system
 */
package bsserver;

import java.util.*;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Reads httpd.conf inserting into hash map.
 * Create one config object at beginning of exection.
 * httpd.conf is read on instantiation.
 * @author marc
 */
public class HttpdConf {

    private final static Vector<String> keyWords = new Vector<String>();
    private HashMap<String, String> httpdConf = new HashMap<String, String>();
    private HashMap<String, String> alias = new HashMap<String, String>();
    private HashMap<String, String> scriptAlias = new HashMap<String, String>();
    private Vector<String> directoryIndex = new Vector<String>();
    private HashMap<String, Vector<String>> directoryAccess = new HashMap<String, Vector<String>>();
    private String serverDirectory = "";

    /**
     * Creates a new Server configuration
     * @param fileName the file to read configuration parameters from
     */
    public HttpdConf(String fileName) {

        BufferedReader input = null;



        /* If system variable $SERVER is set, the value is captured from
         * the system into serverDirectory.  If it is not defined,
         * serverDirectory is set to current working directory.
         */
        if ((serverDirectory = System.getenv("SERVER")) != null) {
            System.out.println("$SERVER is defined and is " + serverDirectory);
        } else {
            System.out.println("$SERVER is NOT defined");
            try {
                serverDirectory = (new File(".").getCanonicalPath());
            } catch (IOException ex) {
                Logger.getLogger(HttpdConf.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        try {
            input = new BufferedReader(new FileReader(fileName));

        } catch (FileNotFoundException e) {
            System.out.println("Error: Configuration file " +
                    fileName + " not found.");
            System.exit(1);
        } catch (Exception e) {
            System.out.println("File IO Error: " + e);
            e.printStackTrace();
            System.exit(1);
        }


        // Set default settings (Listen port, etc.)

        httpdConf.put("Listen", "80");
        httpdConf.put("TypesConfig", "MIME.types");
        httpdConf.put("LogFile", "access.log");
        httpdConf.put("ServerRoot", serverDirectory);
        httpdConf.put("ServerName", "Trevor Blackstone, Marc Sosnick");
        httpdConf.put("AuthFilename", ".htaccess");
        httpdConf.put("DocumentRoot", serverDirectory + "/public_html");
        httpdConf.put("MaxThreads", "100");

        System.out.println(serverDirectory + "/public_html");

        directoryIndex.add("index.html");

        StreamTokenizer s = new StreamTokenizer(input);

        s.resetSyntax();
        s.whitespaceChars(1, 32);
        s.wordChars(33, 126);
        s.quoteChar('"');
        s.commentChar('#');
        s.eolIsSignificant(true);
        s.slashSlashComments(false);
        s.slashStarComments(false);

        try {
            while (s.nextToken() != StreamTokenizer.TT_EOF) {
                String keyword = null;
                if (s.ttype == StreamTokenizer.TT_WORD || s.ttype == '"') {

                    // get keyword at beginning of line
                    keyword = s.sval.replace("$SERVER", serverDirectory);

                    // Handle simple Keyword Value pairs
                    if (keyWords.contains(keyword)) {
                        if (s.nextToken() == StreamTokenizer.TT_WORD ||
                                s.ttype == '"') {
                            httpdConf.put(keyword, s.sval);
                        } else {
                            System.err.println("Syntax error in " + fileName +
                                    " : line " + s.lineno());
                        }

                    // Handle special keywords that are not single values

                    } else if (keyword.startsWith("<")) {

                        // Is a directory directive. directory contained between
                        // < > brackets.  Store the directory, and the directive
                        // that follows.

                        // account for spaces in directory name.  keep reading
                        // tokens until one with a > is found.  If none found
                        // at the end of the line, error and exit.
                        int sNext;
                        String newKeyword = keyword;
                        String nextKeyword = null;


                        if (!keyword.endsWith(">")) {

                            do {
                                sNext = s.nextToken();
                                if (s.sval != null) {
                                    newKeyword = newKeyword.concat(" ").concat(s.sval);
                                }
                            } while (sNext != StreamTokenizer.TT_EOL &&
                                    sNext != StreamTokenizer.TT_EOF &&
                                    !s.sval.contains(">"));


                            if (sNext == StreamTokenizer.TT_EOL ||
                                    sNext == StreamTokenizer.TT_EOF) {
                                System.err.println("Syntax error in " + fileName +
                                        " : line " + s.lineno());
                                System.exit(0);
                            } else if (!s.sval.endsWith(">")) {

                                System.err.println("Syntax error in " + fileName +
                                        " : line " + s.lineno());
                                System.err.println("Must have space between the " +
                                        "end of a directory tag and the directive");
                                System.exit(0);
                            }

                            keyword = newKeyword;

                        }
                        String accessDirectoryName = keyword.replace("<", "").replace(">", "");


                        // get remaining tokens until the end of line is reached
                        String accessDirVal = "";

                        do {
                            sNext = s.nextToken();
                            if (s.sval != null) {
                                accessDirVal = accessDirVal + " \""+ s.sval + "\"";
                            }
                        } while (sNext != StreamTokenizer.TT_EOL && sNext != StreamTokenizer.TT_EOF);

                        accessDirVal = accessDirVal.trim();

                        // now we have the directory and entire line, store it
                        if (directoryAccess.containsKey(accessDirectoryName.trim())) {
                            directoryAccess.get(accessDirectoryName.trim()).add(accessDirVal);


                        } else {

                            directoryAccess.put(accessDirectoryName.trim(),
                                    new Vector<String>());
                            directoryAccess.get(accessDirectoryName.trim()).add(accessDirVal);

                        }

                    } else if (keyword.equalsIgnoreCase("DirectoryIndex")) {
                        s.nextToken();
                        while (s.ttype == StreamTokenizer.TT_WORD ||
                                s.ttype == '"') {
                            directoryIndex.add(s.sval);
                            s.nextToken();
                        }

                    } else if (keyword.equalsIgnoreCase("Alias")) {
                        s.nextToken();
                        if (s.ttype == StreamTokenizer.TT_WORD ||
                                s.ttype == '"') {
                            String aliasPath = s.sval;
                            s.nextToken();
                            if (s.ttype == StreamTokenizer.TT_WORD ||
                                    s.ttype == '"') {
                                alias.put(aliasPath, s.sval);

                            } else {
                                System.err.println("Syntax error in " + fileName +
                                        " : line " + s.lineno());
                                throw new IOException();
                            }
                        } else {
                            System.err.println("Syntax error in " + fileName +
                                    " : line " + s.lineno());
                            throw new IOException();
                        }

                    } else if (keyword.equalsIgnoreCase("ScriptAlias")) {
                        s.nextToken();
                        if (s.ttype == StreamTokenizer.TT_WORD ||
                                s.ttype == '"') {
                            String aliasPath = s.sval;
                            s.nextToken();
                            if (s.ttype == StreamTokenizer.TT_WORD ||
                                    s.ttype == '"') {
                                scriptAlias.put(aliasPath, s.sval);

                            } else {
                                System.err.println("Syntax error in " + fileName +
                                        " : line " + s.lineno());
                                throw new IOException();
                            }
                        } else {
                            System.err.println("Syntax error in " + fileName +
                                    " : line " + s.lineno());
                            throw new IOException();
                        }
                    }


                }
            }
        } catch (IOException ex) {
            Logger.getLogger(HttpdConf.class.getName()).log(Level.SEVERE, null, ex);

        }



        // Fixup all maps, replacing $SERVER with correct string

        for (String x : httpdConf.keySet()) {
            httpdConf.put(x, httpdConf.get(x).replace("$SERVER", serverDirectory));
        }


        Vector<String> newDirectoryIndex = new Vector<String>();
        for (String x : directoryIndex) {
            newDirectoryIndex.add(x.replace("$SERVER", serverDirectory));
        }
        directoryIndex = newDirectoryIndex;

        for (String x : alias.keySet()) {
            System.out.println(x + " -> " + alias.get(x));
            alias.put(x, alias.get(x).replace("$SERVER", serverDirectory));
        }

        for (String x : scriptAlias.keySet()) {
            System.out.println(x + " -> " + scriptAlias.get(x));
            scriptAlias.put(x, scriptAlias.get(x).replace("$SERVER", serverDirectory));
        }



        // display information in httpdConf if anyone cares
        System.out.println("***httpdConf: ");
        for (String x : httpdConf.keySet()) {
            System.out.println(x + " -> " + httpdConf.get(x));
        }
        System.out.println();

        System.out.print("*** Directory Index: ");
        for (String x : directoryIndex) {
            System.out.print(x + " ");
        }
        System.out.println();

        System.out.println("*** Alias paths: ");
        for (String x : alias.keySet()) {
            System.out.println(x + " -> " + alias.get(x));
        }

        System.out.println("*** ScriptAlias paths: ");
        for (String x : scriptAlias.keySet()) {
            System.out.println(x + " -> " + scriptAlias.get(x));
        }

    }

    /**
     * Returns the value of a key, corresponding to the httpd.conf
     * variable.
     * @param key name of an httpd.conf variable name.
     * @return value corresponding to the httpd.conf variable name
     * specified.  Pathnames resolved to absolute paths including
     * $SERVER environment variable.
     */
    public String get(String key) {

        return httpdConf.get(key);

    }

    /**
     * Returns absolute path to the relative path passed in
     * @param httpPath the relative path to resolve
     * @return the absolute-ified path
     */
    public String absolutePath(String httpPath) {

        for (String scriptPath : scriptAlias.keySet()) {
            if (httpPath.startsWith(scriptPath)) {
                // Trim off beginning alias, and extra path if present
                httpPath = httpPath.substring(scriptPath.length());
                int extraPathPosition = httpPath.indexOf('/', 1);
                if (extraPathPosition > -1) {
                    httpPath = httpPath.substring(0, extraPathPosition);
                }

                httpPath = scriptAlias.get(scriptPath) + httpPath;
                return httpPath;
            }
        }

        for (String aliasPath : alias.keySet()) {
            if (httpPath.startsWith(aliasPath)) {
                httpPath = alias.get(aliasPath) + httpPath.substring(aliasPath.length());
                return httpPath;
            }
        }

        String docRoot = get("DocumentRoot");
        return docRoot + httpPath;

    }

    /**
     * Returns extra path info of given path
     * @param httpPath the path to extract extra path info from
     * @return any extra path info if present
     */
    public String extraPathInfo(String httpPath) {

        for (String scriptPath : scriptAlias.keySet()) {
            if (httpPath.startsWith(scriptPath)) {
                // Trim off beginning alias and script name
                httpPath = httpPath.substring(scriptPath.length());
                int extraPathPosition = httpPath.indexOf('/', 1);
                if (extraPathPosition > -1) {
                    httpPath = httpPath.substring(extraPathPosition);
                }
                return httpPath;
            }
        }

        return "";
    }

    /**
     * Returns array of filenames as default filenames for unspecified
     * filenames in directory
     * @return string array of filenames
     */
    public Vector<String> getDirectoryIndex() {
        return directoryIndex;
    }

    /**
     * Identifies if the absolute path starts with a defined script directory.
     * @param path
     * @return true if the path starts with a CGI script path,
     * otherwise, false.
     */
    public boolean isScript(String path) {

        for (String scriptPath : scriptAlias.keySet()) {
            if (path.startsWith(scriptPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the value of the $SERVER directive
     * @return reutrns the value of the $SERVER directive
     */
    public String getServerDirectory() {
        return serverDirectory;
    }

    /**
     * Returns a string vector containing all values associated with
     * a particular directory, created with <directory> directives.
     * @param directory directory to get values from, absolute path
     * @return empty vector if no directory, or a vector containing
     * all values associated with this directory from <directory> directies.
     */
    public Vector<String> getDirectoryAccess(String directory) {

        if (directoryAccess.containsKey(directory)) {
            return directoryAccess.get(directory);
        } else {
            return null;
        }

    }


    static {
        /* Define the list of valid keywords with a keyword value structure
         * more complex keyword-value combinations must be decoded separately
         * this hash table will make it easier to view code in switch table
         * defined below.
         * All two token, i.e. keyword-value pairs will be stored in the
         * httpdConf map.
         */

        keyWords.add("ServerRoot");
        keyWords.add("ServerAdmin");
        keyWords.add("DocumentRoot");
        keyWords.add("Listen");
        keyWords.add("LogFile");
        keyWords.add("AccessFileName");
        keyWords.add("MaxThreads");
        keyWords.add("KeepAlive");
        keyWords.add("KeepAliveTimeOut");



    }
}
